/* ===========================================================================

	JSML Panel.js

	Copyright 2012 John Dunning.  All rights reserved.
	fw@johndunning.com
	http://johndunning.com/fireworks

   ======================================================================== */


/*
	This is an example JSML panel.  You can replace the code below with your
	own, then click in the panel and press F5.  The panel will reload and
	evaluate your new code. 
*/


try {

(function()
{
		// these variablea are private to the functions defined in this closure
	var _constrainProportions = false;
	var _panelInfo = null;

	
	// =======================================================================
	/*
		returns position and size of the current selection in reference to one
		of the corners: top-left, top-middle, etc.
	*/
	function getRelativeBounds(
		inRefPoint)
	{
		var dom = fw.getDocumentDOM();

		if (!dom || fw.selection.length == 0) {
				// there's no document open or nothing is selected
			return null;
		}

		var bounds = dom.getSelectionBounds();
		var width = bounds.right - bounds.left;
		var height = bounds.bottom - bounds.top;
		var x = bounds.left;
		var y = bounds.top;

			// figure out the multipliers we'll use adjust to the XY position 
			// relative to the current reference point.  for example, the X
			// coordinate of the top middle reference point (TM) is half the
			// element's width from its bounds.left value and 0 from its bounds.top.
		var offsets = {
			TL: [0, 0],
			TM: [.5, 0],
			TR: [1, 0],
			ML: [0, .5],
			MM: [.5, .5],
			MR: [1, .5],
			BL: [0, 1],
			BM: [.5, 1],
			BR: [1, 1]
		}[inRefPoint];

		if (!offsets) {
			return null;
		}

		return {
			x: x + width * offsets[0],
			y: y + height * offsets[1],
			width: width,
			height: height
		};
	}


	// =======================================================================
	/*
		sets the position of the current selection in reference to one of the
		corners
	*/
	function setRelativeXY(
		inRefPoint,
		inX,
		inY)
	{
		var dom = fw.getDocumentDOM();

		if (!dom || fw.selection.length == 0) {
				// there's no document open or nothing is selected
			return null;
		}

		var bounds = dom.getSelectionBounds();
		var width = bounds.right - bounds.left;
		var height = bounds.bottom - bounds.top;

		var offsets = {
			TL: [0, 0],
			TM: [-.5, 0],
			TR: [-1, 0],
			ML: [0, -.5],
			MM: [-.5, -.5],
			MR: [-1, -.5],
			BL: [0, -1],
			BM: [-.5, -1],
			BR: [-1, -1]
		}[inRefPoint];

		if (!offsets) {
			return null;
		}

		dom.moveSelectionTo(
			{ x: inX + width * offsets[0], y: inY + height * offsets[1] },
			false, true);
	}


	// =======================================================================
	/*
		returns an array of statements that sets the ConstrainIcon element's
		source and tooltip depending on whether we're currently in constrain mode
	*/
	function getConstrainIconResult()
	{
		return [
				// there are two PNGs for the two states of the icon, stored in
				// a folder called "Transform" in "Command Panels".  we build up
				// the path to the images from the path to our SWF.
			["ConstrainIcon", "source",
				_panelInfo.directory + _panelInfo.name + "/transform-constrain-" +
				(_constrainProportions ? "on" : "off") + ".png"],
			["ConstrainIcon", "toolTip",
				(_constrainProportions ? "Constrain proportions when resizing" :
					"Don't constrain proportions when resizing")]
		];
	}


	// =======================================================================
	/*
		initializes the state of the panel when it first opens by handling the
		onFwStartMovie event
	*/
	function onFwStartMovie(
		inEvent)
	{
			// get the name and directory of our SWF, so that we know where we are
		_panelInfo = fwlib.panel.getCurrentInfo();

			// first update the elements with the info for the current
			// selection, if any, since their values default to 0
		updateDisplay(inEvent);

			// add the statements that update the state of the ConstrainIcon by
			// concatenating them to the result set by updateDisplay.  no image
			// path is specified in the JSML below, so the icon is blank up to now.
		inEvent.result = inEvent.result.concat(getConstrainIconResult());
	}


	// =======================================================================
	/*
		handles the change event from all of the NumericStepper elements 
	*/
	function onStepperChange(
		inEvent)
	{
		var dom = fw.getDocumentDOM();

		if (!dom || fw.selection.length == 0) {
				// there's no document open or nothing is selected
			return;
		}

			// do different things depending on whether it was the X or Y field
			// that changed or the Width or Height field
		switch (inEvent.targetName) {
			case "X":
			case "Y":
					// just move the selection to whatever points are specified
					// in the XY fields
				setRelativeXY(inEvent.currentValues.RefPoint, inEvent.currentValues.X,
					inEvent.currentValues.Y);
				break;

			case "Width":
			case "Height":
				var newWidth = inEvent.currentValues.Width;
				var newHeight = inEvent.currentValues.Height;

					// get the current size of the selection
				var bounds = dom.getSelectionBounds();
				var width = bounds.right - bounds.left;
				var height = bounds.bottom - bounds.top;

				if (inEvent.targetName == "Width") {
					bounds.right = bounds.left + newWidth;

					if (_constrainProportions) {
							// constrain the height to be proportional to the width
						newHeight = (newWidth / width) * height;
						bounds.bottom = bounds.top + newHeight;

							// update the Height field to reflect the new value
						inEvent.result = ["Height", "value", newHeight];
					}
				} else {
					bounds.bottom = bounds.top + newHeight;

					if (_constrainProportions) {
							// constrain the width to be proportional to the height
						newWidth = (newHeight / height) * width;
						bounds.right = bounds.left + newWidth;

							// update the Width field to reflect the new value
						inEvent.result = ["Width", "value", newWidth];
					}
				}

					// now that the bounds have been calculated we can resize
					// the selection, then move it to the right position given
					// the current ref point
				dom.setSelectionBounds(bounds, "autoTrimImages transformAttributes");
				setRelativeXY(inEvent.currentValues.RefPoint, inEvent.currentValues.X,
					inEvent.currentValues.Y);
				break;
		}
	}


	// =======================================================================
	/*
		updates the NumericStepper elements based on the current selection
	*/
	function updateDisplay(
		inEvent)
	{
			// default to showing 0 for these values 
		var width = 0,
			height = 0,
			x = 0,
			y = 0,
			relativeBounds = getRelativeBounds(inEvent.currentValues.RefPoint);

			// only update the fields if there's a document open
			// and something is selected
		if (relativeBounds) {
			x = relativeBounds.x;
			y = relativeBounds.y;
			width = relativeBounds.width;
			height = relativeBounds.height;
		}

			// update the fields in the panel with the new size and position values
		inEvent.result = [
			["Width", "value", width],
			["Height", "value", height],
			["X", "value", x],
			["Y", "value", y]
		];
	}


	// =======================================================================
	/*
		this is a convenience function that returns an object describing a
		NumericStepper element.  since we're going to create 4 nearly identical
		elements below, we can simplify the code a bit by factoring all the
		common properties into this function, rather than repeating the same
		code over and over.
	*/
	function numericStepper(
		inName,
		inTooltip)
	{
			// the only way the various NumericSteppers differ is in their
			// name and tooltip
		return { NumericStepper: {
			name: inName,
			width: 65,
			value: 0,
			stepSize: .1,
			maximum: 100000,
			minimum: -100000,
			toolTip: inTooltip,
			events: {
					// all of the elements should call onStepperChange to handle change
					// events.  the handler will check which element triggered
					// the event and behave accordingly.
				change: onStepperChange
			}
		} };
	}


	// =======================================================================
	/*
		another convenience function that generates the code to build the
		array of radio buttons for choosing the reference point.  having the
		duplicate code in one place made it easier to experiment with things
		like changing the scale value and adding event handlers.
	*/
	function refPointControl()
	{
			// a local function that builds a RadioButton
		function createRadioButton(
			inArgs)
		{
			return { RadioButton: {
					// all of the RadioButtons are part of this group
				groupName: "RefPoint",
				value: inArgs.value,
				x: inArgs.x,
				y: inArgs.y,
					// scale the button to 60% of its normal size
				scaleX: .6,
				scaleY: .6,
					// we disable keyboard focus on the radio buttons because the
					// focus ring doesn't get scaled, even though the RadioButton
					// is scaled, which looks weird
				focusEnabled: false,
				events: {
						// when the radio button is clicked, we need to update the
						// other fields to show the XY position relative to the
						// new point 
					click: updateDisplay
				}
			} };
		}


			// we'll build up the list of RadioButton elements in this array
		var radioButtons = [];
			// there are 10px from the origin of one button to the next
		var stepX = 10;
		var stepY = 10;
			// we'll start the first row of buttons 12px from the top of the
			// Canvas, to vertically center the cluster 
		var paddingTop = 12;
		var rowLabels = ["T", "M", "B"];
		var colLabels = ["L", "M", "R"];
		var x = 0, y = 0;

		for (var row = 0; row < rowLabels.length; row++) {
				// move down 10px for each row
			y = row * stepX + paddingTop;

			for (var col = 0; col < colLabels.length; col++) {
					// move right 10px for each column
				x = col * stepX;

					// create the RadioButton at the current XY position
				radioButtons.push(createRadioButton({
						// its value is, e.g., "T" + "R"
					value: rowLabels[row] + colLabels[col],
					x: x,
					y: y
				}));
			}
		}

			// select the top-left radio button by default
		radioButtons[0].RadioButton.selected = true;

			// the parent of the RadioButtons we just created is a Canvas, which
			// we use so that we can precisely position the child RadioButtons.
			// return the code to describe the Canvas and all its children.
		return { Canvas: {
			width: 3 * stepX,
			height: 3 * stepY + paddingTop,
			toolTip: "Reference point",
			children: radioButtons
		} }
	}


	// =======================================================================
	/*
		this object describes the layout of the Flex elements in the panel
	*/
	var jsml = {
			// set up handlers for the Fireworks events we care about
		events: {
			onFwStartMovie: onFwStartMovie,
				// these 3 Fireworks events are all handled by the same function,
				// since they all can cause the current selection to change
			onFwActiveSelectionChange: updateDisplay,
			onFwActiveDocumentChange: updateDisplay,
			onFwObjectSettingChange: updateDisplay
		},
			// create default styles for certain elements in our panel
		css: {
			HBox: {
					// this is the spacing between children of the HBox
				horizontalGap: 2
			},
			Form: {
				paddingLeft: 0,
				paddingRight: 0,
				paddingTop: 2,
				paddingBottom: 2,
				verticalGap: 4,
				indicatorGap: 0
			},
			FormItem: {
				labelWidth: 20
			},
				// at almost half their normal size, the buttons are a little
				// faint, so make the dot and the borders darker
			RadioButton: {
				iconColor: 0x000000,
				borderColor: 0x333333
			}
		},
		children: [
				// we want the following elements lined up horizontally, so they're
				// all children of a single HBox
			{ HBox: {
				children: [
						// Form elements arrange their children vertically, and
						// we want 2 columns of text fields, so we'll use 2
						// fixed-width form elements next to each other
					{ Form: {
						width: 87,
						children: [
							{ FormItem: {
								label: "W:",
								children: [
										// create a NumericStepper with a name of
										// "Width" and a tooltip of "Width of
										// selection in pixels".  this will be the
										// only child of the FormItem.
									numericStepper("Width", "Width of selection in pixels")
								]
							} },
							{ FormItem: {
								label: "H:",
								children: [
									numericStepper("Height", "Height of selection in pixels")
								]
							} }
						]
					} },
					{ Image: {
						name: "ConstrainIcon",
						width: 11,
						height: 56,
							// using buttonMode causes the finger pointer to
							// appear over the Image
						buttonMode: true,
						style: {
							verticalAlign: "middle"
						},
						events: {
							click: function(inEvent)
							{
									// toggle the current constrain state
								_constrainProportions = !_constrainProportions;
								
									// we want to toggle the icon when we're clicked
								inEvent.result = getConstrainIconResult(inEvent.panel);
							}
						}
					} },
					{ Form: {
						width: 90,
						children: [
							{ FormItem: {
								label: "X:",
								children: [
									numericStepper("X", "X position of selection")
								]
							} },
							{ FormItem: {
								label: "Y:",
								children: [
									numericStepper("Y", "Y position of selection")
								]
							} }
						]
					} },
						// insert the code to generate the array of RadioButtons
						// to the right of the XY fields
					refPointControl()
				]
			} }
		]
	};


		// we make this UI description available to the panel by registering it.
		// when our associated panel is opened, it will use the above object to
		// render the UI, and call the functions to handle the events.
	fwlib.panel.register(jsml);
})();

} catch (exception) {
	alert([exception, exception.lineNumber, exception.fileName].join("\n"));
}
